﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace YouTubeStormer
{
    public partial class Main : Form
    {
        ConfigForm config = new ConfigForm();
        Captcha captchaService = new Captcha();
        Communications communications;
        Client[] ClientList;
        BackgroundWorker cycleWorker;
        bool waitingMessageSent = false;
        Random ran = new Random();
        System.Windows.Forms.Timer cycleTimer = new System.Windows.Forms.Timer();
        int nextCycleTick = 0;
        bool started = false;

        public Main()
        {
            InitializeComponent();
            Icon = Properties.Resources.stormIcon;
            
            BuildClientList();
            communications = new Communications(null);
            numericUpDownCycleInterval.Value = Properties.Settings.Default.CycleInterval;

            cycleWorker = new BackgroundWorker();
            cycleWorker.DoWork += cycleWorker_DoWork;

            cycleTimer.Interval = 1000;
            cycleTimer.Tick += cycleTimer_Tick;
            cycleTimer.Start();
        }

        void cycleTimer_Tick(object sender, EventArgs e)
        {
            if (started)
            {
                if (!cycleWorker.IsBusy)
                    if (nextCycleTick >= Properties.Settings.Default.CycleInterval * 60)
                    {
                        stats.Statistics.NextCycle = 0;
                        nextCycleTick = 0;
                        cycleWorker.RunWorkerAsync();
                    }
                    else
                        nextCycleTick++;
            }
            stats.Statistics.NextCycle = (Properties.Settings.Default.CycleInterval * 60) - nextCycleTick;
            stats.Statistics.WorkingClients = GetBusyClientCount();
        }

        void cycleWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (!LoginToCaptchaService())
            {
                log.PerformLog("Failed to login to captcha service");
                return;
            }

            log.PerformLog("# Beginning Cycle " + (stats.Statistics.Cycles + 1) + " #");

            foreach (Configuration.VideoInfo videoInfo in Configuration.ParseVideos())
            {
                if (VideoIsBusy(videoInfo.VideoID))
                {
                    log.PerformLog("Last cycle client still working on {LINK}", link: Urls.GetVideoUrl(videoInfo.VideoID), linkText: videoInfo.VideoID);
                    continue;
                }

                var comment = Configuration.FindComment(communications, videoInfo.VideoID, deepSearch: true);
                if (comment != null && !comment.Spammed)
                {
                    if (comment.HighestRated)
                    {
                        log.PerformLog("Comment is highest rated on {LINK}", link: Urls.GetVideoUrl(videoInfo.VideoID), linkText: videoInfo.VideoID);
                        stats.Statistics.HighestRatedSeen++;
                    }
                    var clientToVote = FindClientToVote(GetAvaiableClients(), comment.ID);
                    if (clientToVote != null)
                    {
                        clientToVote.PerformVote(videoInfo.VideoID, comment);
                    }
                    else
                    {
                        stats.Statistics.Skipped++;
                        log.PerformLog("Skipped {LINK}", link: Urls.GetVideoUrl(videoInfo.VideoID), linkText: videoInfo.VideoID);
                    }
                }
                else
                {
                    if (comment != null && comment.Spammed)
                    {
                        stats.Statistics.SpammedComments++;
                        log.PerformLog("Spammed comment on {LINK}", link: Urls.GetVideoUrl(videoInfo.VideoID), linkText: videoInfo.VideoID);
                    }
                    var clientForComment = GetAvaiableClients()[0];
                    clientForComment.PerformComment(videoInfo.VideoID, Configuration.GenerateComment(videoInfo.Tag));
                }
            }

            stats.Statistics.Cycles++;
        }

        bool VideoIsBusy(string videoID)
        {
            foreach (Client client in GetAvaiableClients(getBusyClients: true))
            {
                if (client.IsBusy && client.LastVideoID == videoID) return true;
            }
            return false;
        }

        Client FindClientToVote(Client[] clients, string commentID)
        {
            var newClients = new List<Client>();
            var voteLog = Configuration.StringCollectionToArray(Properties.Settings.Default.VoteLog);
            foreach (Client client in clients)
            {
                if (!voteLog.Contains(client.Account.Username + ":" + commentID))
                    newClients.Add(client);
            }
            if (newClients.Count == 0) return null;

            return newClients[0];
        }

        Client[] GetAvaiableClients(bool getBusyClients = false)
        {
            var newClients = new List<Client>();
            foreach (Client client in ClientList)
            {
                if (!getBusyClients && !client.IsBusy) 
                    newClients.Add(client);
                else if (getBusyClients) 
                    newClients.Add(client);
            }
            if (newClients.Count == 0)
            {
                if (!waitingMessageSent)
                {
                    log.PerformLog("Waiting for available clients...");
                    waitingMessageSent = true;
                }
                Thread.Sleep(1000);
                return GetAvaiableClients();
            }
            waitingMessageSent = false;
            return newClients.OrderBy(x => ran.Next()).ToArray();
        }

        int GetBusyClientCount()
        {
            int count = 0;
            foreach (Client client in ClientList)
                if (client.IsBusy) count++;
            return count;
        }

        private void buttonConfig_Click(object sender, EventArgs e)
        {
            if (!config.Visible)
            {
                config.ShowDialog();
                if (config.AccountProxyBindsChanged)
                {
                    BuildClientList();
                }
            }
        }

        private void numericUpDownCycleInterval_ValueChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.CycleInterval = (int)numericUpDownCycleInterval.Value;
            Properties.Settings.Default.Save();
        }

        bool LoginToCaptchaService()
        {
            return captchaService.PerformLogin(Properties.Settings.Default.DeathByCaptchaUsername, Properties.Settings.Default.DeathByCaptchaPassword);
        }

        void BuildClientList()
        {
            var clientList = new List<Client>();
            foreach (string accountLine in Properties.Settings.Default.Accounts)
            {
                var accountParsed = Configuration.ParseAccount(accountLine);
                if (accountParsed != null)
                {
                    var client = new Client(accountParsed, captchaService);
                    client.LoginPerformed += client_LoginPerformed;
                    client.VotePerformed += client_VotePerformed;
                    client.CommentPerformed += client_CommentPerformed;
                    client.DecaptchaPerformed += client_DecaptchaPerformed;
                    client.ConnectionError += client_ConnectionError;
                    client.CaptchaError += client_CaptchaError;
                    clientList.Add(client);
                }
                else
                {
                    MessageBox.Show("Improper account/proxy bind string: " + Environment.NewLine + accountLine);
                }
            }
            ClientList = clientList.ToArray();
        }

        void client_CaptchaError(Client sender)
        {
            stats.Statistics.CaptchaErrors++;
            log.PerformLog("Captcha Service errored on " + sender.Account.Username);
        }

        void client_ConnectionError(Client sender)
        {
            stats.Statistics.ConnectionErrors++;
            log.PerformLog("Connection error on " + sender.Account.Username);
        }

        void client_DecaptchaPerformed(Client sender, bool successful, bool commentCaptcha)
        {
            if (successful)
            {
                if (commentCaptcha) stats.Statistics.DecaptchasComments++;
                else stats.Statistics.DecaptchasMain++;
                log.PerformLog("Decaptcha performed on " + sender.Account.Username + (commentCaptcha ? " for commenting" : " for main page"));
            }
            else
            {
                if (commentCaptcha) stats.Statistics.DecaptchasCommentsFailed++;
                else stats.Statistics.DecaptchasMainFailed++;
                log.PerformLog("Decaptcha failed on " + sender.Account.Username + (commentCaptcha ? " for commenting" : " for main page"));
            }
        }

        void client_CommentPerformed(Client sender, bool successful, bool pending)
        {
            if (successful)
            {
                if (pending)
                {
                    Configuration.RemoveVideo(sender.LastVideoID);
                    stats.Statistics.PendingRemoved++;
                }

                stats.Statistics.Posted++;
                log.PerformLog("Commented on {LINK} with " + sender.Account.Username + (pending ? " comment pending" : ""), link: Urls.GetVideoUrl(sender.LastVideoID), linkText: sender.LastVideoID);
            }
            else
            {
                stats.Statistics.PostingErrors++;
                log.PerformLog("Failed to comment on {LINK} with " + sender.Account.Username, link: Urls.GetVideoUrl(sender.LastVideoID), linkText: sender.LastVideoID);
            }
        }


        void client_VotePerformed(Client sender, bool successful)
        {
            if (successful)
            {
                stats.Statistics.Votes++;
                log.PerformLog("Voted on {LINK} with " + sender.Account.Username, link: Urls.GetVideoUrl(sender.LastVideoID), linkText: sender.LastVideoID);
            }
            else
            {
                stats.Statistics.VoteErrors++;
                log.PerformLog("Failed to voted on {LINK} with " + sender.Account.Username, link: Urls.GetVideoUrl(sender.LastVideoID), linkText: sender.LastVideoID);
            }
        }

        void client_LoginPerformed(Client sender, bool successful)
        {
            if (successful)
            {
                stats.Statistics.Logins++;
                log.PerformLog("Logged in with " + sender.Account.Username);
            }
            else
            {
                stats.Statistics.LoginErrors++;
                log.PerformLog("Failed to logged in with " + sender.Account.Username);
            }
        }

        string buttonStormCancel = "Stop";
        string buttonStormStart = "Start";
        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (buttonStart.Text == buttonStormStart)
            {
                buttonStart.Text = buttonStormCancel;
                started = true;
                nextCycleTick = Properties.Settings.Default.CycleInterval * 60;
            }
            else
            {
                buttonStart.Text = buttonStormStart;
                started = false;
            }
        }
    }
}
